package com.cjj.common.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.SimpleColumnWidthStyleStrategy;
import com.cjj.common.entity.ExportExcelData;
import com.cjj.common.framework.excel.handler.RedMarkCellWriteHandler;
import com.cjj.common.framework.exception.BusinessException;
import com.cjj.common.framework.exception.ExportExcelException;
import com.cjj.common.properties.ExportProperties;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;

/**
 * 通用Excel导出
 *
 * @author DoyuTu
 */
@Slf4j
@Component
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExportExcelUtil {

    private static ExportProperties exportProperties;

    public void setExportProperties(@Autowired ExportProperties exportProperties) {
        ExportExcelUtil.exportProperties = exportProperties;
    }

    public static void simpleExportExcel(@NotEmpty List<ExportExcelData> dataList,
                                         @NotBlank String filename,
                                         @NotNull HttpServletRequest request,
                                         @NotNull HttpServletResponse response) throws ExportExcelException {
        simpleExportExcel(dataList, ExcelTypeEnum.XLSX, filename, request, response);
    }

    private static void writeExcelData(List<ExportExcelData> dataList, ExcelWriterBuilder writer) throws ExportExcelException {
        ExportExcelData excelData;
        List<? extends BaseRowModel> list;
        Class<? extends BaseRowModel> clazz;
        ExcelWriter excelWriter = writer.build();
        for (int i = 0; i < dataList.size(); i++) {
            excelData = dataList.get(i);
            list = excelData.getDataList();
            clazz = CollectionUtils.isEmpty(list) ? excelData.getClazz() : list.get(0).getClass();
            if (clazz == null) {
                throw new BusinessException("导出Excel失败");
            }
            Field[] declaredFields = clazz.getDeclaredFields();
            try {
                for (Field field : declaredFields) {
                    if (String.class.isAssignableFrom(field.getType())) {
                        for (BaseRowModel baseRowModel : list) {
                            ReflectionUtils.makeAccessible(field);
                            String str = (String) field.get(baseRowModel);
                            // XLS最大长度限制
                            int maxLength = 32767;
                            if (StrUtil.length(str) > maxLength) {
                                ReflectionUtils.setField(field, baseRowModel, StrUtil.sub(str, 0, maxLength));
                            }
                        }
                    }
                }
            } catch (IllegalAccessException e) {
                log.error("导出失败", e);
                throw new ExportExcelException();
            }
            WriteSheet writeSheet = new WriteSheet();
            writeSheet.setClazz(clazz);
            writeSheet.setAutoTrim(true);
            writeSheet.setSheetNo(i + 1);
            writeSheet.setSheetName(excelData.getSheetName());
            Optional.ofNullable(excelData.getColumnWidthMap())
                    .filter(columnWidthMap -> !columnWidthMap.isEmpty())
                    .ifPresent(writeSheet::setColumnWidthMap);
            excelWriter.write(list, writeSheet);
        }
        excelWriter.finish();
    }

    /**
     * 按照模板导出 模板类，@see <a href="https://github.com/alibaba/easyexcel/blob/master/quickstart.md#有java模型映射">模板类</a>
     * 简单导出Web下载Excel，web控制类必须返回 void
     *
     * @param dataList      数据列表
     * @param excelTypeEnum 导出Excel的格式
     * @param filename      文件名，不需要后缀名
     * @param response      {@link HttpServletResponse}
     * @throws ExportExcelException 导出下载Excel异常
     */
    public static void simpleExportExcel(@NotEmpty List<ExportExcelData> dataList,
                                         @NotNull ExcelTypeEnum excelTypeEnum,
                                         @NotBlank String filename,
                                         @NotNull HttpServletRequest request,
                                         @NotNull HttpServletResponse response) throws ExportExcelException {
        if (CollUtil.isEmpty(dataList)) {
            dataList = Collections.emptyList();
        }
        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
            HorizontalCellStyleStrategy horizontalCellStyleStrategy = getHorizontalCellStyleStrategy();
            buildAttachmentProp(filename, excelTypeEnum, request, response);
            ExcelWriterBuilder writeHandler = EasyExcel.write(out)
                    .excelType(excelTypeEnum)
                    .inMemory(true)
                    .needHead(true)
                    .autoCloseStream(false)
                    .registerWriteHandler(new SimpleColumnWidthStyleStrategy(30))
                    .registerWriteHandler(horizontalCellStyleStrategy)
                    .registerWriteHandler(new RedMarkCellWriteHandler());
            writeExcelData(dataList, writeHandler);
        } catch (IOException e) {
            log.error("导出下载Excel失败");
            log.error(e.getMessage(), e);
            throw new ExportExcelException();
        } finally {
            if (out != null) {
                try {
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    //Ignore
                }
            }
        }
    }

    private static String writeExcel(@NotEmpty List<ExportExcelData> dataList, @NotNull ExcelTypeEnum excelTypeEnum, @NotBlank String filename) throws ExportExcelException {
        String basePath = exportProperties.getExcel().getBasePath();
        String formatDate = DateUtil.format(new Date(), "yyyyMMdd");
        String newPath;
        File newFile;
        do {
            newPath = StrUtil.format("{}/{}/{}-{}.{}", basePath, formatDate, filename, IdUtil.fastSimpleUUID(), excelTypeEnum.getValue());
            FileUtil.mkParentDirs(newPath);
            newFile = new File(newPath);
        } while (newFile.exists() && newFile.isFile());

        HorizontalCellStyleStrategy horizontalCellStyleStrategy = getHorizontalCellStyleStrategy();

        FileOutputStream out;
        try {
            out = new FileOutputStream(newFile);
        } catch (FileNotFoundException e) {
            log.error(e.getMessage(), e);
            throw new ExportExcelException();
        }
        ExcelWriterBuilder writeHandler = EasyExcel.write(out)
                .excelType(excelTypeEnum)
                .inMemory(true)
                .needHead(true)
                .autoCloseStream(false)
                .registerWriteHandler(new SimpleColumnWidthStyleStrategy(30))
                .registerWriteHandler(horizontalCellStyleStrategy)
                .registerWriteHandler(new RedMarkCellWriteHandler());
        writeExcelData(dataList, writeHandler);
        return newPath;
    }

    private static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() {
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        headWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        headWriteCellStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontName("宋体");
        headWriteFont.setFontHeightInPoints((short) 10);
        headWriteFont.setBold(true);
        headWriteCellStyle.setWriteFont(headWriteFont);
        headWriteCellStyle.setBorderTop(BorderStyle.THIN);
        headWriteCellStyle.setBorderRight(BorderStyle.THIN);
        headWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        headWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        contentWriteCellStyle.setShrinkToFit(true);
        contentWriteCellStyle.setWrapped(true);
        WriteFont contentWriteFont = new WriteFont();
        contentWriteFont.setFontName("宋体");
        contentWriteFont.setFontHeightInPoints((short) 10);
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
        contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
        contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }

    private static void buildAttachmentProp(String filename,
                                            ExcelTypeEnum excelTypeEnum,
                                            HttpServletRequest request,
                                            HttpServletResponse response) throws UnsupportedEncodingException {
        filename = filename + "_" + DateUtil.format(new Date(), "yyyyMMdd");
        buildAttachmentProp(filename, excelTypeEnum.getValue(), request, response);
    }

    public static void buildAttachmentProp(String filename,
                                           String postfix,
                                           HttpServletRequest request,
                                           HttpServletResponse response) throws UnsupportedEncodingException {
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + ";charset=UTF-8");
        String value;
        String ua = request.getHeader(HttpHeaders.USER_AGENT).toUpperCase();
        filename = filename + postfix;
        filename = URLEncoder.encode(filename, "utf-8");
        if (StrUtil.containsAny(ua, "MSIE", "TRIDENT", "EDGE")) {
            value = "attachment;filename=\"" + StrUtil.replace(filename, "+", "%20") + "\"";
        } else {
            if (ua.contains("FIREFOX")) {
                value = "attachment; filename*=\"utf8''" + filename + "\"";
            }else if (ua.contains("POSTMAN")){
                value = "attachment;filename*=utf-8''" + filename ;
            } else {
                value = "attachment; filename=\"" + filename + "\"";
            }
        }
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, value);
        response.setCharacterEncoding("utf-8");
    }

    public static Map<Integer, Integer> getColumnWidthMap(int num) {
        if (num <= 0) {
            return Collections.emptyMap();
        }
        Map<Integer, Integer> columnWidthMap = new HashMap<>(num);
        for (int i = 0; i < num; i++) {
            columnWidthMap.put(i, (int) ((30 + 0.72) * 256));
        }
        return columnWidthMap;
    }

    public static String getRedMark(String text) {
        Mark mark = Mark.RED;
        return mark.getPre() + StrUtil.nullToDefault(text, "") + mark.getPost();
    }

    @Getter
    @AllArgsConstructor
    public enum Mark {

        /**
         *
         */
        RED("<red>", "</red>", Font.COLOR_RED, "红色");

        private String pre;

        private String post;

        private short color;

        private String desc;

    }

    @Data
    public static class ExcelIndex {

        private int startIndex;

        private int endIndex;

    }

}
